              package com.java.diagnostics.visualizer.impl.axes;
              
              import com.java.diagnostics.visualizer.data.DataPoint;
              import com.java.diagnostics.visualizer.data.SourceData;
              import com.java.diagnostics.visualizer.data.axes.Axis;
              import com.java.diagnostics.visualizer.data.axes.DataAxis;
              import com.java.diagnostics.visualizer.data.axes.UnitConverter;
              import com.java.diagnostics.visualizer.data.axes.UnitConverterDefinition;
              import com.java.diagnostics.visualizer.exceptions.GCAndMemoryVisualizerException;
              import com.java.diagnostics.visualizer.impl.factory.LogFactory;
              import com.java.diagnostics.visualizer.impl.util.Messages;
              import com.java.diagnostics.visualizer.properties.OutputProperties;
              import java.text.MessageFormat;
              import java.util.HashMap;
              import java.util.Map;
              import java.util.Set;
              import java.util.logging.Level;
              import java.util.logging.Logger;
              
              public abstract class DataAxisImpl
                implements DataAxis, Comparable
              {
                private static final String CLOSE_PARENTHESIS = ")";
                private static final String OPEN_PARENTHESIS = " (";
                private Map<String, UnitConverter> converters;
                private UnitConverterDefinition definition;
                private static final String[] UNIT_TEMPLATE_ARRAY = new String[0];
                protected AxisImpl axis;
                private boolean supportsNormalisation;
                private double rawOffset = 0.0D;
              
                private static final Logger TRACE = LogFactory.getTrace(DataAxisImpl.class);
              
                public DataAxisImpl(Axis axis, OutputProperties properties) {
                  this(axis, true, properties);
                }
              
                public DataAxisImpl(Axis axis, boolean supportsNormalisation, OutputProperties properties)
                {
                  this.axis = ((AxisImpl)axis);
                  this.supportsNormalisation = supportsNormalisation;
              
                  if (axis != null) {
                    this.converters = new HashMap();
              
                    NormalisingIdentityConverter plainConverter = new NormalisingIdentityConverter(
                      axis.getLabel(), axis.getBaseUnitName(), 
                      supportsNormalisation);
              
                    this.definition = plainConverter.getDefinition();
                    this.converters.put(this.definition.getUnitName(), plainConverter);
                  }
                }
              
                public boolean supportsNormalisation()
                {
                  return this.supportsNormalisation;
                }
              
                public String getLabel()
                {
                  String label = null;
                  if ((this.definition != null) && 
                    (this.definition.getLabel() != null)) {
                    label = this.definition.getLabel();
                  }
              
                  if (label == null) {
                    label = this.axis.getLabel();
                  }
                  return label;
                }
              
                public String getUnits() {
                  return this.definition.getUnitName();
                }
              
                public void addUnitConverter(UnitConverter unitConverter) throws GCAndMemoryVisualizerException
                {
                  if ((unitConverter != null) && (this.axis != null)) {
                    UnitConverterDefinition newDefinition = unitConverter
                      .getDefinition();
                    if (this.axis.getBaseUnitName().equals(newDefinition.getBaseUnitName()))
                    {
                      unitConverter.setOffset(this.rawOffset);
                      this.converters.put(newDefinition.getUnitName(), unitConverter);
                    } else {
                      throw new GCAndMemoryVisualizerException(
                        MessageFormat.format(
                        Messages.getString("DataAxisImpl.units.warning"), 
                        new Object[] { 
                        this.axis.getBaseUnitName(), 
                        unitConverter.getDefinition()
                        .getBaseUnitName() }));
                    }
                  }
                }
              
                public String[] getPossibleUnits()
                {
                  return (String[])this.converters.keySet().toArray(UNIT_TEMPLATE_ARRAY);
                }
              
                public boolean isPossibleUnit(String units) {
                  return this.converters.containsKey(units);
                }
              
                public UnitConverterDefinition getConverterDefinition(String unitName) {
                  UnitConverterDefinition definition = null;
                  UnitConverter possibleConverter = 
                    (UnitConverter)this.converters
                    .get(unitName);
              
                  if (possibleConverter != null) {
                    definition = possibleConverter.getDefinition();
                  }
                  return definition;
                }
              
                public final double convert(DataPoint dataPoint, String units)
                {
                  UnitConverter possibleConverter = getUnitConverter(units);
              
                  if (possibleConverter != null) {
                    if ((possibleConverter instanceof NonLinearConverter)) {
                      return ((NonLinearConverter)possibleConverter).convert(dataPoint);
                    }
                    return possibleConverter.convert(getRawValue(dataPoint));
                  }
                  return (0.0D / 0.0D);
                }
              
                protected abstract double getRawValue(DataPoint paramDataPoint);
              
                private UnitConverter getUnitConverter(String units)
                {
                  synchronized (this.converters) {
                    return (UnitConverter)this.converters.get(units);
                  }
                }
              
                public final String format(DataPoint dataPoint, String units)
                {
                  UnitConverter possibleConverter = getUnitConverter(units);
              
                  if (possibleConverter != null) {
                    if ((possibleConverter instanceof NonLinearConverter)) {
                      return ((NonLinearConverter)possibleConverter).format(dataPoint);
                    }
                    return possibleConverter.format(getRawValue(dataPoint));
                  }
                  return NumberFormatter.prettyString(getRawValue(dataPoint));
                }
              
                public final String formatWithUnits(DataPoint dataPoint, String units)
                {
                  UnitConverter possibleConverter = getUnitConverter(units);
              
                  if (possibleConverter != null) {
                    if ((possibleConverter instanceof NonLinearConverter)) {
                      return ((NonLinearConverter)possibleConverter).formatWithUnits(dataPoint);
                    }
                    return possibleConverter.formatWithUnits(getRawValue(dataPoint));
                  }
                  String formattedNumber = NumberFormatter.prettyString(getRawValue(dataPoint));
                  return MessageFormat.format("{0} {1}", new Object[] { formattedNumber, units });
                }
              
                public final String formatUnconvertedWithUnits(double value, String units)
                {
                  UnitConverter possibleConverter = getUnitConverter(units);
              
                  if (possibleConverter != null) {
                    return possibleConverter.formatUnconvertedWithUnits(value);
                  }
                  String formattedNumber = NumberFormatter.prettyString(value);
                  return MessageFormat.format("{0} {1}", new Object[] { formattedNumber, units });
                }
              
                public String formatUnconverted(double n, String units)
                {
                  UnitConverter possibleConverter = getUnitConverter(units);
                  if (possibleConverter != null) {
                    return possibleConverter.formatUnconverted(n);
                  }
                  return NumberFormatter.prettyString(n);
                }
              
                public String formatUnconverted(double n, int fractionDigits, String units)
                {
                  UnitConverter possibleConverter = getUnitConverter(units);
                  if (possibleConverter != null) {
                    return possibleConverter.formatUnconverted(n, fractionDigits);
                  }
                  return NumberFormatter.prettyString(n);
                }
              
                public final double parse(String number, String units)
                {
                  UnitConverter possibleConverter = getUnitConverter(units);
                  if (possibleConverter != null) {
                    return possibleConverter.parseUnconverted(number);
                  }
              
                  return Double.parseDouble(number);
                }
              
                public String toString()
                {
                  if (this.definition != null) {
                    String label = this.definition.getLabel();
                    if (label == null) {
                      label = this.axis.getLabel();
                    }
                    if (this.axis.getType() != 1) {
                      return label + " (" + this.definition.getUnitName() + 
                        ")";
                    }
                    return label;
                  }
              
                  return this.axis.toString();
                }
              
                public boolean equals(Object object)
                {
                  return this == object;
                }
              
                public int compareTo(Object o) {
                  if (this == o)
                    return 0;
                  if ((o instanceof DataAxisImpl)) {
                    return getLabel().compareTo(((DataAxisImpl)o).getLabel());
                  }
                  return 1;
                }
              
                public int hashCode()
                {
                  return toString().hashCode();
                }
              
                private void setOffset(double offset)
                {
                  if (TRACE.isLoggable(Level.FINER)) {
                    TRACE.finer(getLabel() + 
                      " updating normalisation offset to " + offset + ".");
                  }
                  this.rawOffset = offset;
                }
              
                public double getNormalisationOffset() {
                  return this.rawOffset;
                }
              
                public void updateNormalisation(SourceData data) {
                  double offset = NormalisationMinimumFinder.findMinimum(data, this);
              
                  setOffset(offset);
              
                  for (UnitConverter converter : this.converters.values())
                    converter.setOffset(offset);
                }
              
                public Axis getAxis()
                {
                  return this.axis;
                }
              }

